#include<map>

#include<formic/command/command.h>
#include<formic/archive.h>
//#include<formic/all_data.h>
#include<formic/exception.h>
#include<formic/mpi/interface.h>

namespace formic {

  /// \brief  a mapping of command names to command function pointers
  std::map<std::string, void (*)(formic::Archive &, formic::AllData &)> command_map;

}

///////////////////////////////////////////////////////////////////////////////////////////////////
/// \brief  Run the command corresponding to the supplied name
///
///
/// \param[in]      command_name    the name of the command to run
/// \param[in,out]  arch            an archive from which the command can read options and/or data
/// \param[in,out]  data            a data object on which the command will operate
///
///////////////////////////////////////////////////////////////////////////////////////////////////
void formic::run_command(const std::string & command_name, formic::Archive & arch, formic::AllData & data) {

  // search for the command in the global command map
  std::map<std::string, void (*)(formic::Archive &, formic::AllData &)>::const_iterator it;
  it = formic::command_map.find(command_name);

  // check that the command was found
  if (it == formic::command_map.end())
    throw formic::Exception("command \"%s\" not found") % command_name;

  // get the function pointer for the command
  void (*fptr)(formic::Archive &, formic::AllData &);
  fptr = it->second;

  // run the command
  fptr(arch, data);

}

///////////////////////////////////////////////////////////////////////////////////////////////////
/// \brief  Run all commands in the specified text file
///
/// \param[in]      filename        the name of the file
/// \param[in,out]  data            a data object on which the commands will operate
///
///////////////////////////////////////////////////////////////////////////////////////////////////
void formic::run_text_commands(const std::string & filename, formic::AllData & data) {

  // get MPI info
  const int nproc  = formic::mpi::size();
  const int myrank = formic::mpi::rank();

  formic::TextArchive arch;

  // open the file on the root process and read it in to a text archive
  if (myrank == 0) {

    std::ifstream ifs(filename.c_str());
    if (!ifs.is_open())
      throw formic::Exception("failed to open file \"%s\" in formic::run_text_commands") % filename;

    while (true) {

      std::string line;
      std::getline(ifs, line);
      if ( !ifs || ifs.eof() )
        break;

      // remove comments, which start with the % character
      const size_t pos = line.find('%');
      if (pos != std::string::npos)
        line.erase(pos); // erases from pos to end

      arch << line;

    }

    ifs.close();

  }

  // one by one, execute the commands in the file
  while (true) {

    // read the keyword
    std::string keyword;
    if (myrank == 0)
      arch >> keyword;
    formic::mpi::bcast(keyword);

    // if there were no more keywords, we are finished
    bool finished = !arch;
    formic::mpi::bcast(finished);
    if (finished) break;

    // run the command corresponding to the keyword
    formic::run_command(keyword, arch, data);

  }

}

///////////////////////////////////////////////////////////////////////////////////////////////////
/// \brief  Run all commands in the specified binary file
///
/// \param[in]      filename        the name of the file
/// \param[in,out]  data            a data object on which the commands will operate
///
///////////////////////////////////////////////////////////////////////////////////////////////////
void formic::run_binary_commands(const std::string & filename, formic::AllData & data) {

  // get MPI info
  const int nproc  = formic::mpi::size();
  const int myrank = formic::mpi::rank();

  // open the binary file on the root process, and empty archives on other processes
  boost::shared_ptr<formic::BinaryFileArchive> aptr;
  if (myrank == 0) {
    boost::shared_ptr<formic::BinaryFileArchive> temp( new formic::BinaryFileArchive(filename, "r") );
    aptr = temp;
  } else {
    boost::shared_ptr<formic::BinaryFileArchive> temp( new formic::BinaryFileArchive() );
    aptr = temp;
  }

  // one by one, execute the commands in the file
  for (bool finished = false; !finished; ) {

    // read the keyword
    std::string keyword;
    if (myrank == 0)
      *aptr >> keyword;
    formic::mpi::bcast(keyword);

    // if there were no more keywords, we are finished
    if (myrank == 0)
      finished = !*aptr;
    formic::mpi::bcast(finished);
    if (finished) break;

    // run the command corresponding to the keyword
    formic::run_command(keyword, *aptr, data);

  }

}

///////////////////////////////////////////////////////////////////////////////////////////////////
/// \brief  add a command to the command system
///
/// \param[in]      command_name    the name of the new command
/// \param[in]      command_func    a pointer to the function for the new command
///
///////////////////////////////////////////////////////////////////////////////////////////////////
void formic::add_command(const std::string & command_name, void (*command_func)(formic::Archive &, formic::AllData &)) {

  if ( formic::command_map.count(command_name) > 0 )
    throw formic::Exception("formic::command_map already has an element with key = \"%s\"") % command_name;

  formic::command_map[command_name] = command_func;

}

///////////////////////////////////////////////////////////////////////////////////////////////////
/// \brief   Function called for the "BinaryCommands" command, which executes the commands found
///          in the named binary file.
///
/// \param[in]     arch         archive to read command arguments from
/// \param[in]     data         data on which to execute the command
///
///////////////////////////////////////////////////////////////////////////////////////////////////
static void formic_BinaryCommands_command_function(formic::Archive & arch, formic::AllData & data) {

  std::string filename;
  formic::mpi::read_and_bcast(arch, filename, "Failed to read filename in formic_BinaryCommands_command_function.");

  formic::run_binary_commands(filename, data);

}

///////////////////////////////////////////////////////////////////////////////////////////////////
/// \brief   Function called for the "TextCommands" command, which executes the commands found
///          in the named text file.
///
/// \param[in]     arch         archive to read command arguments from
/// \param[in]     data         data on which to execute the command
///
///////////////////////////////////////////////////////////////////////////////////////////////////
static void formic_TextCommands_command_function(formic::Archive & arch, formic::AllData & data) {

  std::string filename;
  formic::mpi::read_and_bcast(arch, filename, "Failed to read filename in formic_TextCommands_command_function.");

  formic::run_text_commands(filename, data);

}

///////////////////////////////////////////////////////////////////////////////////////////////////
/// \brief   Add command related commands to the formic command system
///
///////////////////////////////////////////////////////////////////////////////////////////////////
void formic::init_command_commands() {

  // add command to run commands from a binary file
  formic::add_command("BinaryCommands", &formic_BinaryCommands_command_function);

  // add command to run commands from a text file
  formic::add_command("TextCommands", &formic_TextCommands_command_function);

}
